package mbp.core;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.apache.log4j.Logger;

import mbp.base.ContractState;
import mbp.base.Settings;

/**
 * Implementuje kontrakt pomiedzy serwerem uslug a klientem
 * @author Wojciech Wilanski
 *
 */
public class Contract 
{
	//Oferta ktorej dotyczy kontrakt - typ oferty
	private Offer offer;
	
	//Identyfikator kontraktu
	private String uuid;
	
	//Nazwa magazynu zasobow
	private String storageName;
	
	//Terminy dostarczenia zasobow wejsciowych od momentu podpisania kontraktu
	private List<Date> stuffValidDate = new ArrayList<Date>();
	
	//Data realizacji kontraktu
	private Date realzeValidDate;
	
	//stan kontraktu
	private ContractState contractState;
	
	private Date frozenDate = null;
	
	private ContractState lastState = null;
	
	// Zmienna licznik, dowolne wykorzystanie
	public int counter = 0;
	
	protected Logger logger = Logger.getLogger(this.getClass());
	
	/**
	 * Konstruktor. Przy tworzeniu kontraktu nalezy podac oferte. Kontrakt dotyczy tej oferty 
	 * @param offer
	 */
	public Contract(Offer offer)
	{
		this.offer = offer;
		this.uuid = offer.GetUUID();
		calculateDates();
		setStorageName("storage");
		this.contractState = ContractState.NEW;
	}
	
	public void suspend()
	{
		if(contractState == ContractState.PRODUCE || 
				contractState == ContractState.REALIZE)
		{
			lastState = ContractState.valueOf(contractState.toString());
			Calendar calendar = Calendar.getInstance();
			frozenDate = calendar.getTime();
			contractState = ContractState.FROZEN;
		}
	}
	
	public void resume()
	{
		if(contractState == ContractState.FROZEN)
		{
			Calendar calendar = Calendar.getInstance();
			Date now = calendar.getTime();
			Long dateToAdd = now.getTime() - frozenDate.getTime();
			for(int i = 0; i<stuffValidDate.size(); i++)
			{
				Date newDate = new Date(stuffValidDate.get(i).getTime() + dateToAdd);
				stuffValidDate.set(i, newDate);
			}
			
			Date newRDate = new Date(realzeValidDate.getTime() + dateToAdd);
			realzeValidDate = newRDate;
			
			contractState = ContractState.valueOf(lastState.toString());
			lastState = null;
			frozenDate = null;
		}
	}
	
	/**
	 * Oblicza daty: dostarczenia zasobow, realizacji uslugi
	 */
	private void calculateDates()
	{
		//Pobranie aktualnej daty
		Calendar calendar = Calendar.getInstance();
		Date dtNow = calendar.getTime();
		stuffValidDate.clear();
		
		//terminy dostarczenia zasobow wejsciowych
		for(int i = 0; i<offer.getResourcesInDeliveryTrems().size(); i++)
		{
			calendar.add(Calendar.SECOND, offer.getResourcesInDeliveryTrems().get(i) * Settings.getMultiplier());
			stuffValidDate.add(calendar.getTime());
			
			calendar.setTime(dtNow);
		}
		
		calendar.add(Calendar.SECOND, offer.getServiceRealizeTerm() * Settings.getMultiplier());
		realzeValidDate = calendar.getTime();
	}
	
	/**
	 * Podpisanie kontraktu i przekazanie zasobow (zaliczka)
	 * @return
	 */
	public boolean signContract(List<Resource> resourceList)
	{	
		if(this.contractState != ContractState.NEW)
		{
			logger.error("Can not process. Contract state is: " + this.contractState + " The state should be: NEW");
			return false;
		}
		
		if(!offer.isOfferValid())
		{
			logger.error("Can not sign contract. The offer in no longer valid !!!");
			return false;
		}
		
		if(offer.getAdvanceRecieved())
		{
			calculateDates();	
		}
		else
		{
			if(offer.addResourcesAdvance((ArrayList<Resource>) resourceList))
			{
				calculateDates();
			}
			else
			{
				logger.error("Not possible to sign contract !!! Error Resource advance");
				return false;
			}
		}
		
		if(offer.getStuffRecieved())
		{
			setContractState(ContractState.PRODUCE);
			logger.info("Contract signed. State set to PRODUCE");
		}
		else
		{
			setContractState(ContractState.REALIZE);
			logger.info("Contract signed. State set to REALIZE");
		}
		return true;
	}
	
	public boolean processContract(Resource resource)
	{
		validateContract();
		
		if(this.contractState != ContractState.REALIZE)
		{
			logger.error("Can not process. Contract state is: " + this.contractState + " The state should be: REALIZE");
			return false;
		}
		
		if(offer.addResourceStuff(resource))
		{
			logger.info("Process contract completed successfully");
			if(offer.getStuffRecieved())
			{
				setContractState(ContractState.PRODUCE);
				logger.info("Setting contract to state: PRODUCE");
			}
			return true;
		}
		else
		{
			logger.error("Process contract error");
			return false;
		}
	}
	
	public List<Resource> finalizeContract(List<Resource> resourceList)
	{
		validateContract();
		if(this.contractState != ContractState.PRODUCE)
		{
			logger.error("Can not finalize. Contract state is: " + this.contractState + " The state should be: PRODUCE");
			return new ArrayList<Resource>();
		}
		
		List<Resource> resourcesOut = offer.executeService((ArrayList<Resource>) resourceList);
		
		if(resourcesOut != null)
		{
			logger.info("Contract finalized.");
			setContractState(ContractState.CLOSED);
			return resourcesOut;
		}
		else
		{
			logger.error("Error finalizig contract");
			return new ArrayList<Resource>();
		}

	}
	
	//Sprawdza terminowosc kontraktu
	public void validateContract()
	{
		Date dtNow = new Date();
		
		switch(contractState)
		{
		//nowo utworzony obiekt
		case NEW:
			logger.info("Contract is NEW, not signed yet.");
			break;
		//kontrakt podpisany, zasoby zaliczka jesli byly konieczne dostarczone 
		//faza dostarczania zasobow towar
		case REALIZE:
			int index = offer.rcInStuff.size();
			
			for(int i = index; i<offer.getResourcesIn(1).size(); i++)
			{
				if(dtNow.after(stuffValidDate.get(i)))
				{
					logger.error("Actual date is: " + dtNow.toString() + 
							" Deadline to provide resource: " + offer.getResourcesIn(1).get(i).getType().getResourceTypeName() + 
							" was at: " + stuffValidDate.get(i).toString() + " Setting contract to state BROKEN !!!");
					setContractState(ContractState.BROKEN);
					break;
				}
			}
			break;
		//oczekiwanie na realizacje kontraktu. Wszystkie zasoby towary jesli
		//byly konieczne zostaly dostarczone
		case PRODUCE:
			if(dtNow.after(realzeValidDate))
			{
//				logger.error("Actual date is: " + dtNow.toString() +
//						" Deadline to realize contract was at: " + realzeValidDate.toString() +
//						" Setting contract to state BROKEN !!!");
//				setContractState(ContractState.BROKEN);
				logger.error("Actual date is: " + dtNow.toString() + 
						" Deadline to realize contract was at: " + realzeValidDate.toString() + 
						" Waiting for payment !!!");
			}
			break;
		}
	}
	
	/**
	 * Zwraca liste typow zasobow ktore nie zostaly jeszcze dostarczone.
	 * Lista jest wg wlasciwej kolejnosci i rozpoczyna sie od pierwszego
	 * niedostarczonego jeszcze zasobu
	 * @return
	 */
	public List<String> getNotRecievedStuff()
	{
		if(contractState != ContractState.REALIZE)
		{
			return null;
		}
		List<String> rcTypes = new ArrayList<String>();
		
		int recieved = offer.getStuffRecievedCount();
		for(int i = recieved; i<offer.getResourcesIn(1).size(); i++)
		{
			String type = offer.getResourcesIn(1).get(i).getType().getResourceTypeName();
			rcTypes.add(type);
		}
		
		return rcTypes;	
	}
	
	public String getClientName() {
		return offer.getClientName();
	}
	
	public String getAgentName() {
		return offer.getAgentName();
	}


	public String getServerName() {
		return offer.getServerName();
	}
	
	public String GetUUID()
	{
		return this.uuid;
	}
	
	public ContractState getContractState() {
		return contractState;
	}

	public void setContractState(ContractState contractState) {
		this.contractState = contractState;
	}

	public String getStorageName() {
		return storageName;
	}

	public void setStorageName(String storageName) {
		this.storageName = storageName;
	}
	
	public Offer getOffer()
	{
		return this.offer;
	}
	
	public ArrayList<Date> getstuffValidDate()
	{
		return (ArrayList<Date>) this.stuffValidDate;
	}
	
	public Date getrealzeValidDate()
	{
		return this.realzeValidDate;
	}

}
